#include "binary-out-stream.hh"
#include "lom-binary-out-stream.hh"
#include "lom-value.hh"
#include "lom-errors.hh"
#include "lom-value-factory.hh"
#include "lom-instance.hh"
#include "lom-class.hh"
#include "lom-stream-handle-map.hh"

using namespace licada;
using namespace lom;
using namespace boost;

namespace lom
{
    template< typename TDataType >
    void write( IBinaryOutStream& proxy
		, TDataType val ) {
	uint8* data = (uint8*)&val;
	for ( unsigned index = 0; index < sizeof( val ); ++index )
	    proxy << data[index];
    }
    template< typename TDataType >
    void writeVec( IBinaryOutStream& proxy
		   , const std::vector< TDataType >& val ) {
	uint32 len = (uint32)val.size();
	write( proxy, len );
	do_all( val, bind( write<TDataType>, ref(proxy), _1 ) );
    }

    void LomBinaryOutStream::operator<<( bool val ) {
	(*outStream) << (uint8) val;
    }
    void LomBinaryOutStream::operator<<( double val ) {
	write( *outStream, val );
    }
    void LomBinaryOutStream::operator<<( uint8 val ) {
	write( *outStream, val );
    }
    void LomBinaryOutStream::operator<<( uint16 val ) {
	write( *outStream, val );
    }
    void LomBinaryOutStream::operator<<( uint32 val ) {
	write( *outStream, val );
    }
    void LomBinaryOutStream::operator<<( int64 val ) {
	write( *outStream, val );
    }
    void LomBinaryOutStream::operator<<( const TDoubleVector& val ) {
	writeVec( *outStream, val );
    }
    void LomBinaryOutStream::operator<<( const TInt64Vector& val ) {
	writeVec( *outStream, val );
    }
    void LomBinaryOutStream::operator<<( const TString& val ) {
	writeVec( *outStream, val );
    }
    void LomBinaryOutStream::operator<<( const TStringVector& val ) {
	uint32 len = val.size();
	write( *outStream, len );
	for ( unsigned index = 0; index < len; ++index )
	    writeVec( *outStream, val[index] );
    }
    void LomBinaryOutStream::operator<<( const TUInt32Vector& val ) {
	writeVec( *outStream, val );
    }
    void LomBinaryOutStream::operator<<( const TDataVector& val ) {
	writeVec( *outStream, val );
    }
    void LomBinaryOutStream::operator<<( TNameType val ) {
	uint32 len = val ? strlen(val) : 0;
	write( *outStream, len );
	for ( uint32 index = 0; index < len; ++index )
	    write( *outStream, val[index] );
    }
    void LomBinaryOutStream::operator<<( lom::EDataType dtype ) {
	uint8 data = (uint8)dtype;
	write( *outStream, data );
    }
    void LomBinaryOutStream::operator<<( const lom::LomValue* val ) {
	if ( val == NULL ) {
	    *this << EDataTypeNone;
	    return;
	}
	*this << val->getDataType();
	switch( val->getDataType() )
	{
	case EDataTypeBool:
	    *this << val->getBool();
	    break;
	case EDataTypeString:
	    *this << val->getString();
	    break;
	case EDataTypeStringV:
	    *this << val->getStringV();
	    break;
	case EDataTypeHandle:
	    *this << outgoingMap->worldToStream( val->getHandle() );
	    break;
	case EDataTypeHandleV:
	    tempHandleV = val->getHandleV();
	    std::transform( tempHandleV.begin(), tempHandleV.end()
			    , tempHandleV.begin()
			    , bind( &ILomOutgoingHandleMap::worldToStream
				    , outgoingMap, _1 ) );
	    *this << tempHandleV;
	    break;
	case EDataTypeDouble:
	    *this << val->getDouble();
	    break;
	case EDataTypeDoubleV:
	    *this << val->getDoubleV();
	    break;
	case EDataTypeInt64:
	    *this << val->getInt64();
	    break;
	case EDataTypeInt64V:
	    *this << val->getInt64V();
	    break;
	case EDataTypeDataV:
	    *this << val->getDataV();
	    break;
	default:
	    throw UnrecognizedDatatype( val->getDataType() );
	}
    }
    static inline void writeVal( LomBinaryOutStream* str, LomValue* val )
    {
	*str << val;
    }
    void LomBinaryOutStream::operator<<( const lom::TValueVector& val )
    {
	uint32 len = val.size();
	*this << len;
	do_all( val, bind( writeVal, this, _1 ) );
    }
    
    void LomBinaryOutStream::operator<<( const lom::LomInstance& val ) {
	*this << val.getClass()->getName().c_str();
	*this << val.getValues();
    }
    
    void LomBinaryOutStream::operator<<( const lom::LomClass& val ) {
	*this << val.getName().c_str();
	*this << val.getProperties();
	*this << val.getDefaults();
    }
    
    static inline void writeProp( LomBinaryOutStream* str, const LomProperty& prop ) {
	*str << prop.name.c_str();
	*str << prop.type;
    }
    
    void LomBinaryOutStream::operator<<( const lom::TPropertyVector& val ) {
	uint32 len = val.size();
	*this << len;
	do_all( val, bind( writeProp, this, _1 ) );
    }
}
